// Copyright (c) 2010 Omer Goshen
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#ifndef OBJECT_H
#define OBJECT_H

#include <map>
#include <vector>
#include <set>

#include "OgreCommon.h"
#include "Property.h"
#include "Value.h"
#include "Function.h"

//{{{ Exceptions
class ObjectException: public std::exception
{
public:
	ObjectException(std::string s) : msg(s) {}
	ObjectException() throw() { }
	virtual ~ObjectException() throw() {}
	virtual const char* what() const throw()
	{
		return msg.c_str();
	}
	std::string msg;
};

class PropertyNotFoundException: public ObjectException
{
public:
	PropertyNotFoundException(std::string s) : ObjectException(s) {}
	PropertyNotFoundException() throw() { }
	virtual ~PropertyNotFoundException() throw() {}
};
//}}}


//{{{ Types
typedef std::vector<IProperty*> PropertyVector;
typedef std::set<IProperty*> PropertySet;
typedef std::list<IProperty*> PropertyList;
typedef std::map<std::string, IProperty*> ProperyMap;

typedef std::map<std::string, IProperty*>::iterator PropertyMapIterator;
typedef std::set<IProperty*>::iterator PropertySetIterator;
typedef std::list<IProperty*>::iterator PropertyListIterator;
//}}}

//{{{ Object
/**
 * \class Object
 * \author gershon
 * \date 07/16/2010
 * \file Object.h
 * \brief A Property bag.
 */
class Object
{
	static Ogre::NameGenerator objectNameGenerator;
public:
	//{{{ Con\Destructors
	/** Empty constructor **/
	Object() : mName(objectNameGenerator.generate()) {}

	/** Default\Named constructor **/
	explicit Object(std::string name) : mName(name) {}

	//!TODO: /** Copy constructor **/ Object(const Object& o) {}

	/** Property array constructor **/
	Object(IProperty* props[]) : mName(objectNameGenerator.generate())
	{
		for(int i=0; props[i]; i++)
			addProperty(props[i]);
	}

	virtual ~Object()
	{
//		PropertyMapIterator it = properties.begin();
//		for(; it != properties.end(); ++it)
//			{
//				delete it->second;
//				it->second = 0;
//			}
		properties.clear();
	}
	//}}}
	
	virtual std::string toString() const;
	
	//{{{ Operators
	bool const				operator==(const Object& o) { return (this==&o); }
	bool 					operator!=(const Object& o) { return (this!=&o); }
	IProperty* 				operator[](const char* name) { return properties[name]; }

	template<typename T>
	T& operator[](const char* name) { return prop_cast_val<T>(properties[name]); }

//!TODO: assignment operator...
//	Object& 				operator =(const Object& o)
//	{
//		if (this != &o)
//			{
//				//!TODO
//			}
//		return *this;
//	}


	friend std::ostream& 	operator<<(std::ostream& stream, Object o)	{ return stream << o.getName() << "[Object]"; }
	//}}}

	//{{{ Functions
	inline bool isNull() const { return this==NULL; }

	/**
	 * \brief Check wheter object has a property
	 * \param name of the property to check
	 * \return true if found in the map
	 */
	inline bool hasProperty(std::string name) { return properties.find(name) != properties.end(); }

	/**
	 * \brief Typed version...
	 */
	template<class T>
	bool hasProperty(std::string name);


	/**
	 * \brief Get an IProperty pointer
	 * \param name of property to get
	 * \return the property
	 * \throw an ObjectException
	 */
	inline IProperty* getIProperty(std::string name)
	{
//		if(!hasProperty(name)) return NULL;
		PropertyMapIterator it = properties.find(name);
		if(it != properties.end())
			{
				return it->second;
			}
		else
			throw PropertyNotFoundException("Unable to get property " + name);

	}

	template<class T>
	/**
	 * \brief Gets a property from this object
	 * \param name of the property
	 * \return
	 */
	Property<T> getProperty(const std::string& name)
	{
		PropertyMapIterator it = properties.find(name);
		if(it == properties.end())
			throw PropertyNotFoundException("Unable to get property " + name);

		Property<T>* property = dynamic_cast< Property<T>* >(it->second);
		if(!property)
			throw ObjectException("Tried to get property " + name + ", but the type was wrong!");

		return *property;
	}

	PropertyVector* getPropertiesByType(const std::string& type)
	{
		return NULL;
	}

	template<class T>
	/**
	 * \brief Gets a property or the supplied default value if it doesn't exist.
	 * \param name of Property to extract from object
	 * \param def  Default value if no property found
	 * \return Either the property's or the default value
	 */
	T getWithDef(const std::string& name, T def)
	{
		return hasProperty(name) ? prop_cast_val<T>(getIProperty(name)) : def;
	}


	template<class T>
	/**
	 * \brief Add a property
	 * \param name 	Property name to add to object
	 * \param value Value for property
	 * \return The Property
	 */
	inline Property<T> addProperty(const std::string& name, const T& value)
	{
		Property<T> *property = new Property<T>(name);
		property->Set(value);
		properties[name] = property;
		return *property;
	}

	/**
	 * \brief Add a property
	 * \param p Pointer to an IProperty
	 */
	inline void addProperty(IProperty* p) { properties[p->getName()] = p; }
		
	//inline Object callMethod(const std::string& name, const Object& args)
	
	inline std::string getName() { return mName; }
	inline void setName(const std::string& name) { mName = name; }
	
	/**
	 * \brief Serializes the object
	 * \return a string with a XML representation of this object
	 */
	std::string getXmlString(int indent=0);

//	static Object fromXmlString(const std::string& str);
	//}}}

	//{{{ Members
	ProperyMap properties;
//	IProperty* properties[];

protected:
	/**
	 * A little meanless in C++, as you have the variable name,
	 * but when passing an Object to script, or serializing, this member will decide its name...
	 */
	std::string mName;
	//}}}
};
//}}}


#endif // OBJECT_H
